package com.ecar.api.pay.ali;

import com.ecar.api.pay.ali.sign.RSA;
import com.ecar.common.datetime.TimeUtil;
import com.ecar.common.util.HttpClientUtil;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.DocumentHelper;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.SortedMap;
import java.util.TreeMap;

/**
 * 类名：AlipaySubmit
 * 功能：支付宝各接口请求提交类
 * 详细：构造支付宝各接口表单HTML文本，获取远程HTTP数据
 * 版本：3.3
 * 日期：2012-08-13
 * 说明：
 * 以下代码只是为了方便商户测试而提供的样例代码，商户可以根据自己网站的需要，按照技术文档编写,并非一定要使用该代码。
 * 该代码仅供学习和研究支付宝接口使用，只是提供一个参考。
 */

public class AlipaySubmit {

	private static Logger log = LoggerFactory.getLogger(AlipaySubmit.class);

	/**
	 * 支付宝提供给商户的服务接入网关URL(新)
	 */
	private static final String ALIPAY_GATEWAY_NEW = "https://mapi.alipay.com/gateway.do?";

	/**
	 * 生成签名结果
	 *
	 * @param sPara 要签名的数组
	 * @return 签名结果字符串
	 */
	public static String buildRequestMysign(Map<String, String> sPara) throws UnsupportedEncodingException {
		String prestr = AlipayCore.createLinkString(sPara); //把数组所有元素，按照“参数=参数值”的模式用“&”字符拼接成字符串
		String mysign = "";
		if (AlipayConfig.sign_type.equals("RSA")) {
			mysign = RSA.sign(prestr, AlipayConfig.private_key, AlipayConfig.input_charset);
		}
		return mysign;
	}

	/**
	 * 生成要请求给支付宝的参数数组
	 *
	 * @param sParaTemp 请求前的参数数组
	 * @return 要请求的参数数组
	 */
	private static Map<String, String> buildRequestPara(Map<String, String> sParaTemp)
		throws UnsupportedEncodingException {
		//除去数组中的空值和签名参数
		Map<String, String> sPara = AlipayCore.paraFilter(sParaTemp);
		//生成签名结果
		String mysign = buildRequestMysign(sPara);
		//签名结果与签名方式加入请求提交参数组中
		sPara.put("sign", mysign);
		sPara.put("sign_type", AlipayConfig.sign_type);

		return sPara;
	}

	/**
	 * 建立请求，以POST表单形式提交
	 *
	 * @param sParaTemp 请求参数数组
	 */
	public static Map<String, String> sendFormRequest(Map<String, String> sParaTemp) {
		//待请求参数数组
		try {
			Map<String, String> sPara = buildRequestPara(sParaTemp);
			String url = ALIPAY_GATEWAY_NEW + "_input_charset=" + AlipayConfig.input_charset;
			String response = HttpClientUtil.httpPost(url, sPara);
			return parseXMLString(response);
		} catch (IOException | DocumentException e) {
			e.printStackTrace();
			return null;
		}
	}

	/**
	 * 用于防钓鱼，调用接口query_timestamp来获取时间戳的处理函数
	 * 注意：远程解析XML出错，与服务器是否支持SSL等配置有关
	 *
	 * @return 时间戳字符串
	 * @throws IOException
	 * @throws DocumentException
	 * @throws MalformedURLException
	 * @noinspection unused, StringBufferMayBeStringBuilder
	 */
	public static String query_timestamp() throws DocumentException, IOException {
		//构造访问query_timestamp接口的URL串
		String strUrl = ALIPAY_GATEWAY_NEW + "service=query_timestamp&partner=" + AlipayConfig.partner
			+ "&_input_charset" + AlipayConfig.input_charset;
		StringBuffer result = new StringBuffer();

		SAXReader reader = new SAXReader();
		Document doc = reader.read(new URL(strUrl).openStream());

		List<Node> nodeList = doc.selectNodes("//alipay/*");

		for (Node node : nodeList) {
			// 截取部分不需要解析的信息
			if (node.getName().equals("is_success") && node.getText().equals("T")) {
				// 判断是否有成功标示
				List<Node> nodeList1 = doc.selectNodes("//response/timestamp/*");
				for (Node node1 : nodeList1) {
					result.append(node1.getText());
				}
			}
		}

		return result.toString();
	}

	private static Map<String, String> parseXMLString(String message)
		throws IOException, DocumentException {
		SortedMap<String, String> messageMap = new TreeMap<>();

		Document doc = DocumentHelper.parseText(message);
		Element root = doc.getRootElement();
		for (Element e : (List<Element>) root.elements()) {
			messageMap.put(e.getName(), e.getText());
		}

		return messageMap;
	}

	/**
	 * 请求支付宝无密退款接口
	 *
	 * @param batchNo    退款批次号，取本次退款的第一个退款单的批次号。
	 * @param batchNum   退款笔数
	 * @param detailData 退款参数集
	 */
	public static boolean sendRefundRequest(String batchNo, String batchNum, String detailData) {
		log.info("支付宝退款参数，batchNo：{}, batchNum：{}，detailData：{}", batchNo, batchNum, detailData);
		//把请求参数打包成数组
		Map<String, String> sParaTemp = new HashMap<>();
		sParaTemp.put("service", AlipayConfig.service);
		sParaTemp.put("partner", AlipayConfig.partner);
		sParaTemp.put("_input_charset", AlipayConfig.input_charset);
		sParaTemp.put("notify_url", AlipayConfig.refund_url);
		sParaTemp.put("seller_user_id", AlipayConfig.seller_user_id);
		sParaTemp.put("refund_date", TimeUtil.formatTime(new Date()));
		sParaTemp.put("batch_no", batchNo);
		sParaTemp.put("batch_num", batchNum);
		sParaTemp.put("detail_data", detailData);

		boolean bSuccess = false;
		Map<String, String> resultMap = sendFormRequest(sParaTemp);
		if (resultMap != null && resultMap.containsKey("is_success")) {
			bSuccess = resultMap.get("is_success").equalsIgnoreCase("T");
		}
		return bSuccess;
	}

	/**
	 * 建立请求，以表单HTML形式构造（默认）
	 * @param sParaTemp 请求参数数组
	 * @param strMethod 提交方式。两个值可选：post、get
	 * @param strButtonName 确认按钮显示文字
	 * @return 提交表单HTML文本
	 */
	public static String buildRequest(Map<String, String> sParaTemp, String strMethod, String strButtonName) {
		try {
			//待请求参数数组
			Map<String, String> sPara = buildRequestPara(sParaTemp);
			List<String> keys = new ArrayList<>(sPara.keySet());

			StringBuilder sbHtml = new StringBuilder();

			sbHtml.append("<form id=\"alipaysubmit\" name=\"alipaysubmit\" action=\"")
				  .append(ALIPAY_GATEWAY_NEW)
				  .append("_input_charset=")
				  .append(AlipayConfig.input_charset)
				  .append("\" method=\"")
				  .append(strMethod)
				  .append("\">");

			for (String key : keys) {
				String value = sPara.get(key);
				sbHtml.append("<input type=\"hidden\" name=\"")
					  .append(key)
					  .append("\" value=\"")
					  .append(value)
					  .append("\"/>");
			}

			//submit按钮控件请不要含有name属性
			sbHtml.append("<input type=\"submit\" value=\"")
				  .append(strButtonName)
				  .append("\" style=\"display:none;\"></form>");
			sbHtml.append("<script>document.forms['alipaysubmit'].submit();</script>");

			return sbHtml.toString();
		} catch (Exception e) {
			e.printStackTrace();
			return null;
		}
	}
}
